#ifndef JSEDITOR_H
#define JSEDITOR_H

#include "js_editor_global.h"
#include "tabsettings.h"
#include "basetextfind.h"
#include "semanticinfo.h"

#include <QPlainTextEdit>
#include <QTimer>
#include <QTimeLine>

class QLineWidget;
class RefactorOverlay;
class AutoCompleter;
class Indenter;
class SemanticInfoUpdater;
class QJSHighLight;
class SemanticHighlighter;
class TextEditorOverlay;
class BaseTextFind;

namespace QmlJS
{
    class ModelManagerInterface;
}

enum PositionOperation
{
    Current = 1,
    EndOfLine = 2,
    StartOfLine = 3,
    Anchor = 4,
    EndOfDoc = 5
};

enum AssistKind
{
    Completion,
    QuickFix,
    FollowSymbol
};

enum AssistReason
{
    IdleEditor,
    ActivationCharacter,
    ExplicitlyInvoked
};

class BaseTextEditorAnimator : public QObject
{
    Q_OBJECT
public:
    BaseTextEditorAnimator(QObject *parent);
    ~BaseTextEditorAnimator();

    inline void setPosition(int position) { m_position = position; }
    inline int position() const { return m_position; }

    void setData(QFont f, QPalette pal, const QString &text);

    void draw(QPainter *p, const QPointF &pos);
    QRectF rect() const;

    inline qreal value() const { return m_value; }
    inline QPointF lastDrawPos() const { return m_lastDrawPos; }

    bool isRunning() const;
public slots:
    void finish();

signals:
    void updateRequest(int position, QPointF lastPos, QRectF rect);


private slots:
    void step(qreal v);

private:
    QTimeLine *m_timeline;
    qreal m_value;
    int m_position;
    QPointF m_lastDrawPos;
    QFont m_font;
    QPalette m_palette;
    QString m_text;
    QSizeF m_size;
};

struct BaseTextEditorPrivateHighlightBlocks
{
    QList<int> open;
    QList<int> close;
    QList<int> visualIndent;
    inline int count() const { return visualIndent.size(); }
    inline bool isEmpty() const { return open.isEmpty() || close.isEmpty() || visualIndent.isEmpty(); }
    inline bool operator==(const BaseTextEditorPrivateHighlightBlocks &o) const {
        return (open == o.open && close == o.close && visualIndent == o.visualIndent);
    }
    inline bool operator!=(const BaseTextEditorPrivateHighlightBlocks &o) const { return !(*this == o); }
};

class JSEDITOR_EXPORT JSEditor : public QPlainTextEdit
{
    Q_OBJECT
public:
    explicit JSEditor(QWidget *parent = 0);
    ~JSEditor();

    enum ExtraSelectionKind
    {
        CurrentLineSelection,
        ParenthesesMatchingSelection,
        CodeWarningsSelection,
        CodeSemanticsSelection,
        UndefinedSymbolSelection,
        UnusedSymbolSelection,
        FakeVimSelection,
        OtherSelection,
        SnippetPlaceholderSelection,
        ObjCSelection,
        DebuggerExceptionSelection,
        NExtraSelectionKinds
    };

    int     position(PositionOperation posOp = Current,
                     int at = -1) const;
    void    setCursorPosition(int pos);
    void    remove(int length);
    void    replace(int length, const QString &string);

    void    insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet);

    QJSHighLight    *get_high_light();

    bool            open(const QString &fileName);
    QString         fileName();
protected:
    friend class QLineWidget;
    friend class RefactorOverlay;
    friend class TextEditorOverlay;

    void    painteLineWidget(QPainter *p);
    int     extraAreaWidth()const;
    void    extraAreaMouseMoveEvent(QMouseEvent *e);
    void    extraAreaLeaveEvent(QEvent *);
    void    extraAreaMousePressEvent(QMouseEvent *e);


    void    toggleBlockVisible(const QTextBlock &block);

    void    updateFoldingHighlight(const QPoint &pos);
    int     visualIndent(const QTextBlock &block) const;

    void    draw_Foliding(QPainter *p,const QTextBlock &block,qreal top,qreal bottom);
    void    drawFoldingMarker(QPainter *painter, const QPalette &pal,
                           const QRect &rect,
                           bool expanded,
                           bool active,
                           bool hovered) const;

    QRect   foldBox();

    void drawCollapsedBlockPopup(QPainter &painter,
                                         const QTextBlock &block,
                                         QPointF offset,
                                         const QRect &clip);
    QTextBlock foldedBlockAt(const QPoint &pos, QRect *box=0) const;

    void    paintEvent(QPaintEvent *e);
    void    clearVisibleFoldedBlock();
    void    indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar);
    void    handleHomeKey(bool anchor);
    bool    camelCaseRight(QTextCursor &cursor, QTextCursor::MoveMode mode);
    bool    camelCaseLeft(QTextCursor &cursor, QTextCursor::MoveMode mode);
    void    setExtraSelections(ExtraSelectionKind kind, const QList<QTextEdit::ExtraSelection> &selections);
    QList<QTextEdit::ExtraSelection> extraSelections(ExtraSelectionKind kind) const;
    void    maybeClearSomeExtraSelections(const QTextCursor &cursor);
    void    indentOrUnindent(bool doIndent);
    bool    tabShouldIndent(const QTextDocument *document, QTextCursor cursor, int *suggestedPosition) const;

    void    handleBackspaceKey();

    QString wordUnderCursor() const;

    void    setSelectedElements();
protected slots:
    void    updateLineWidget();
    void    highlightBlocks();
    void    editorContentsChange(int position, int charsRemoved, int charsAdded);
    void    cursorPositionChanged_slot();
    void    matchParentheses();
    void    animateUpdate(int position, QPointF lastPos, QRectF rect);
    void    animate_delete();
    void    acceptNewSemanticInfo(const SemanticInfo &semanticInfo);
    void    onDocumentUpdated(QmlJS::Document::Ptr doc);
    void    updateSemanticInfo();
    void    updateSemanticInfoNow();

    void    updateUses();
    void    updateUsesNow();

    void    reparseDocumentNow();
    void    reparseDocument();

    void    modificationChanged(bool changed);

    void    updateCursorPositionNow();
    void    highlightSearchResults(const QString &txt, FindFlags findFlags);

    void    setFindScope(const QTextCursor &start, const QTextCursor &end,
                                      int verticalBlockSelectionFirstColumn,
                                      int verticalBlockSelectionLastColumn);
protected:
    void    updateExtraAreaWidth();
    void    resizeEvent(QResizeEvent *e);
    void    mouseMoveEvent(QMouseEvent *e);
    void    moveCursorVisible(bool ensureVisible);
    void    timerEvent(QTimerEvent *e);
    void    mousePressEvent(QMouseEvent *e);
    void    focusOutEvent(QFocusEvent *e);
    void    focusInEvent(QFocusEvent *e);
    void    wheelEvent(QWheelEvent *e);
    void    keyPressEvent(QKeyEvent *e);

    bool    cursorMoveKeyEvent(QKeyEvent *e);
signals:
    void selectedElementsChanged(QList<QmlJS::AST::UiObjectMember*> offsets,
                                 const QString &wordAtCursor);
protected:
    QLineWidget     *m_line_widget;
    int             m_extraAreaHighlightFoldedBlockNumber;
    QTimer*         m_highlightBlocksTimer;
    BaseTextEditorPrivateHighlightBlocks m_highlightBlocksInfo;
    QTextCharFormat m_ifdefedOutFormat;
    int             m_visibleFoldedBlockNumber;
    int             m_suggestedVisibleFoldedBlockNumber;
    QBasicTimer     m_foldedBlockTimer;
    RefactorOverlay *m_refactorOverlay;
    bool            m_mouseOnFoldedMarker;
    bool            m_contentsChanged;
    bool            m_assistRelevantContentAdded;
    AutoCompleter   *m_autocompleter;
    TabSettings     m_tab_settings;
    Indenter        *m_indenter;
    QTimer          *m_parenthesesMatchingTimer;
    QTextCharFormat m_matchFormat;
    QTextCharFormat m_mismatchFormat;
    QTextCharFormat m_textMatchFormat;
    QList<QTextEdit::ExtraSelection> m_extraSelections[NExtraSelectionKinds];
    BaseTextEditorAnimator  *m_animator;
    SemanticInfoUpdater     *m_semanticinfo_updater;
    SemanticInfo            m_semanticinfo;
    QJSHighLight            *m_high_light;
    SemanticHighlighter     *m_semanticHighlighter;
    QmlJS::ModelManagerInterface *m_modelManager;
    QTimer *m_updateSemanticInfoTimer;
    QTimer *m_updateUsesTimer;
    QTextCharFormat m_occurrencesFormat;
    QString         m_file_name;
    QTimer *m_updateDocumentTimer;
    int m_oldCursorPosition;
    QTimer *m_cursorPositionTimer;
    TextEditorOverlay *m_overlay;
    TextEditorOverlay *m_snippetOverlay;
    TextEditorOverlay *m_searchResultOverlay;
    BaseTextFind       *m_text_find;
    QRegExp             m_searchExpr;
    FindFlags           m_findFlags;
    QTextCursor m_findScopeStart;
    QTextCursor m_findScopeEnd;
    int m_findScopeVerticalBlockSelectionFirstColumn;
    int m_findScopeVerticalBlockSelectionLastColumn;

public:
    QVector<QTextLayout::FormatRange> m_diagnosticRanges;
};

extern QList<JSEditor*>  g_editors;

#endif // JSEDITOR_H
